package main

import (
	"bytes"
	"fmt"
	"github.com/axgle/mahonia"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strings"
	"syscall"
	"time"
)

type GetTpl struct {
	gurl    string
	gname   string
	gdomain string
}

var tplPath = "template"
var httpClient *http.Client = &http.Client{
	Transport: &http.Transport{
		Dial: func(netw, addr string) (net.Conn, error) {
			c, err := net.DialTimeout(netw, addr, time.Second*3)
			if err != nil {
				return nil, err
			}
			return c, nil
		},
		MaxIdleConnsPerHost:   10,
		ResponseHeaderTimeout: time.Second * 2,
	},
}

func main() {
	for {
		var aurl, aname string
		fmt.Println(`
  =============================================
=
= [仿站模板工具]
=
= 第一步、输入要抓取的网页地址
= 第二步、输入要保存的文件名，如index.tpl
= 第三步、等待下载完成后，重复以上步骤下载下一个页面
= 开始下载将html、css、js和图片等资源下载到template目录并分类
= 本程序会自动将相应路径改为保存的路径
=
  ===========================================
`)
		fmt.Print("请输入要仿模板页面的网址（q 退出）：")
		fmt.Scanln(&aurl)
		if aurl == "q" {
			break
		}
		if aurl == "about" {
			fmt.Println("作者：一曲忧伤 451309839@qq.com")
			time.Sleep(5 * time.Second)
			continue
		}
		fmt.Print("请输入要保存的文件名（q 退出）：")
		fmt.Scanln(&aname)
		if aname == "q" {
			break
		}

		os.MkdirAll(tplPath+"/style", 0777)
		os.MkdirAll(tplPath+"/images", 0777)
		theTpl := GetTpl{trueScheme(aurl), aname, getDomain(aurl)}
		if err := theTpl.getWeb(); err != nil {
			fmt.Printf("%s", err)
		} else {
			fmt.Println("下载已完成")
		}
	}
}

func (theTpl *GetTpl) getWeb() (err error) {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
		}
	}()
	body, err := getUrl(theTpl.gurl)
	if err != nil {
		return
	}
	html := string(body)
	html = theTpl.localHtml(html)
	writeStringFile(tplPath+"/"+theTpl.gname, html)
	return
}

func getUrl(url string) ([]byte, error) {
	resp, err := httpClient.Get(url)
	if err != nil {
		fmt.Println("Error url: " + url)
		return nil, err
	}
	defer resp.Body.Close()
	return ioutil.ReadAll(resp.Body)
}

//html链接转换为绝对链接
func (theTpl *GetTpl) localHtml(html string) string {

	//将href链接改为绝对链接
	reg := regexp.MustCompile(`(?i:\s+href)=['"]?([^'"\ ]+)['"]?`)
	html = reg.ReplaceAllStringFunc(html, func(str string) string {
		reg := regexp.MustCompile(`(?i:\s+href)=['\"]?([^'\"\ ]+)['\"]?`)
		match := reg.FindStringSubmatch(str)
		turl := match[1]

		if turl != "" {
			newurl := trueurl(turl, theTpl.gdomain)
			fmt.Println(newurl)
			name, ext := urlFilename(newurl)
			if ext == "css" {
				err := downCss(newurl, tplPath+"/style/"+name, theTpl.gdomain)
				if isError(err) {
					str = strings.Replace(str, turl, newurl, 1)
				} else {
					str = strings.Replace(str, turl, "/style/"+name, 1)
				}
			} else {
				str = strings.Replace(str, turl, newurl, 1)
			}
		}
		return str
	})

	//将src链接改为绝对链接
	reg = regexp.MustCompile(`(?i:\s+src)=['"]?[^'"\ ]+['"]?`)
	html = reg.ReplaceAllStringFunc(html, func(str string) string {
		reg := regexp.MustCompile(`(?i:\s+src)=['\"]?([^'\"\ ]+)['\"]?`)
		match := reg.FindStringSubmatch(str)
		turl := match[1]

		if turl != "" {
			newurl := trueurl(turl, theTpl.gdomain)
			name, ext := urlFilename(newurl)
			if ext == "js" || ext == "png" || ext == "jpg" || ext == "gif" {
				fmt.Println(newurl)
				err := httpCopy(newurl, tplPath+"/images/"+name)
				if isError(err) {
					str = strings.Replace(str, turl, newurl, 1)
				} else {
					str = strings.Replace(str, turl, "/images/"+name, 1)
				}
			} else {
				str = strings.Replace(str, turl, newurl, 1)
			}
		}
		return str
	})

	reg = regexp.MustCompile(`(?i:background)=['\"]?[^'\"\ ]+['\"]?`)
	html = reg.ReplaceAllStringFunc(html, func(str string) string {
		reg := regexp.MustCompile(`['\"]?([^'\"\ ]+)['\"]?`)
		match := reg.FindStringSubmatch(str)
		bg := match[1]
		if bg != "" {
			newurl := trueurl(bg, theTpl.gdomain)
			name, ext := urlFilename(newurl)
			if ext == "js" || ext == "png" || ext == "jpg" || ext == "gif" {
				fmt.Println(newurl)
				err := httpCopy(newurl, tplPath+"/images/"+name)
				if isError(err) {
					str = strings.Replace(str, bg, newurl, 1)
				} else {
					str = strings.Replace(str, bg, "/images/"+name, 1)
				}
			} else {
				str = strings.Replace(str, bg, newurl, 1)
			}
		}
		return str
	})

	reg = regexp.MustCompile(`(?i:\:url)\(['\"]?[^'\"\)]+['\"]?\)`)
	html = reg.ReplaceAllStringFunc(html, func(str string) string {
		reg := regexp.MustCompile(`\(['\"]?([^'\"\)]+)['\"]?\)`)
		match := reg.FindStringSubmatch(str)
		link := match[1]
		if link != "" {
			var newlink string
			if isTrueurl(newlink) {
				newlink = strings.TrimSpace(link)
			} else {
				newlink = theTpl.gdomain + "/" + link
			}
			fmt.Println(newlink)
			name, ext := urlFilename(newlink)
			if ext == "css" {
				err := downCss(newlink, tplPath+"/style/"+name, theTpl.gdomain)
				if err == nil {
					link = name
				}
			} else if ext == "png" || ext == "jpg" || ext == "gif" {
				err := httpCopy(newlink, tplPath+"/images/"+name)
				if err == nil {
					link = "../images/" + name
				}
			}
		}
		return ":url('" + link + "')"
	})

	return html
}

//下载css及调用的图片
func downCss(aurl, savepath, domain string) error {
	resp, err := http.Get(aurl)
	defer resp.Body.Close()
	pix, err := ioutil.ReadAll(resp.Body)
	cssContent := string(pix)
	reg := regexp.MustCompile(`url\(['\"]?[^'\"\)]+['\"]?\)`)
	cssContent = reg.ReplaceAllStringFunc(cssContent, func(str string) string {
		reg := regexp.MustCompile(`\(['\"]?([^'\"\)]+)['\"]?\)`)
		match := reg.FindStringSubmatch(str)
		link := match[1]
		if link != "" {
			var newlink string
			if link[0] == '/' {
				newlink = domain + link
			} else if isTrueurl(newlink) {
				newlink = strings.TrimSpace(link)
			} else {
				uri := substr(aurl, 0, strings.LastIndex(aurl, "/")+1)
				newlink = uri + link
			}
			name, ext := urlFilename(newlink)
			if ext == "css" {
				err = downCss(newlink, tplPath+"/style/"+name, domain)
				if err == nil {
					link = name
				}
			} else if ext == "png" || ext == "jpg" || ext == "gif" {
				err = httpCopy(newlink, tplPath+"/images/"+name)
				if err == nil {
					link = "../images/" + name
				}
			}
		}
		return "url('" + link + "')"
	})
	out, err := os.Create(savepath)
	defer out.Close()
	_, err = io.Copy(out, bytes.NewReader([]byte(cssContent)))
	return err
}

func isTrueurl(aurl string) bool {
	if strings.HasPrefix(strings.ToLower(aurl), "http://") || strings.HasPrefix(strings.ToLower(aurl), "https://") || strings.HasPrefix(strings.ToLower(aurl), "ftp://") {
		return true
	}
	return false
}

func trueurl(aurl, domain string) string {
	switch aurl[0] {
	case '/':
		return domain + aurl
	case '#':
		return aurl
	default:
		if isTrueurl(aurl) {
			return aurl
		}
		return domain + "/" + aurl
	}
}

func trueScheme(aurl string) string {
	if aurl[0] == '/' {
		return aurl
	}
	if !isTrueurl(aurl) {
		return "http://" + aurl
	}
	return aurl
}

func urlFilename(url string) (string, string) {
	path := strings.Split(url, "/")
	var name, ext string
	if len(path) > 1 {
		name = path[len(path)-1]
		if strings.Index(name, "?") != -1 {
			name = substr(name, 0, strings.Index(name, "?"))
		}
	}
	if strings.LastIndex(name, ".") != -1 {
		ext = strings.ToLower(substr(name, strings.LastIndex(name, ".")+1, len(name)))
	}
	return name, ext
}

func httpCopy(aurl, savepath string) error {
	//name, _ := urlFilename(url)
	//savepath := savepath + "/" + name
	out, err := os.Create(savepath)
	defer out.Close()
	resp, err := http.Get(aurl)
	defer resp.Body.Close()
	pix, err := ioutil.ReadAll(resp.Body)
	_, err = io.Copy(out, bytes.NewReader(pix))
	return err
}

func getDomain(aurl string) string {
	u, e := url.ParseRequestURI(trueScheme(aurl))
	if e != nil {
		return ""
	}
	if u.Scheme == "" {
		u.Scheme = "http"
	}
	return u.Scheme + "://" + u.Host
}

func isError(err error) bool {
	if err != nil {
		fmt.Println(err.Error())
		return true
	}
	return false
}

//用最笨的方法检测字符串编码
func checkEncoder(str string) string {
	coders := []string{"UTF8", "GBK", "BIG5"}
	for _, c := range coders {
		enc := mahonia.NewEncoder(c)
		dec := mahonia.NewDecoder(c)
		if enc.ConvertString(dec.ConvertString(str)) == str {
			return c
		}
	}
	return ""
}

/*************
输出颜色字体  颜色参考
颜色：蓝	1
字体颜色：绿	2
字体颜色：红	4
前景色高亮显示	8
背景颜色：蓝	16
背景颜色：绿	32
背景颜色：红 64
背景色高亮显示	128
*************/
func colorPrintln(s string, i int) {
	kernel32 := syscall.NewLazyDLL("kernel32.dll")
	proc := kernel32.NewProc("SetConsoleTextAttribute")
	handle, _, _ := proc.Call(uintptr(syscall.Stdout), uintptr(i)) //12 Red light

	fmt.Println(s)

	handle, _, _ = proc.Call(uintptr(syscall.Stdout), uintptr(7)) //White dark
	CloseHandle := kernel32.NewProc("CloseHandle")
	CloseHandle.Call(handle)
}

func substr(str string, start, length int) string {
	rs := []rune(str)
	rl := len(rs)
	end := 0

	if start < 0 {
		start = rl - 1 + start
	}
	end = start + length

	if start > end {
		start, end = end, start
	}

	if start < 0 {
		start = 0
	}
	if start > rl {
		start = rl
	}
	if end < 0 {
		end = 0
	}
	if end > rl {
		end = rl
	}

	return string(rs[start:end])
}
func writeStringFile(filename, body string) (int, error) {
	var f *os.File
	if checkFileIsExist(filename) {
		f, _ = os.OpenFile(filename, os.O_WRONLY|os.O_TRUNC, 0666)
	} else {
		f, _ = os.Create(filename) //创建文件
	}
	defer f.Close()
	return io.WriteString(f, body) //写入文件(字符串)
}

func readStringFile(filename string) string {
	b, err := ioutil.ReadFile(filename)
	if isError(err) {
		return ""
	}
	return string(b)
}

//判断文件是否存在  存在返回 true 不存在返回false
func checkFileIsExist(filename string) bool {
	var exist = true
	if _, err := os.Stat(filename); os.IsNotExist(err) {
		exist = false
	}
	return exist
}
